<!DOCTYPE html><html lang="zh-cn"><head><meta charset="utf-8"><meta name="X-UA-Compatible" content="IE=edge"><title> 锁的实现(PV原语) · JohnnyRian NameSpace</title><meta name="description" content="锁的实现(PV原语) - JohnnyRian"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="icon" href="/favicon.ico"><link rel="stylesheet" href="/css/prontera.css"><link rel="search" type="application/opensearchdescription+xml" href="http://renleilei1992.gitee.io/atom.xml" title="JohnnyRian NameSpace"><meta name="generator" content="Hexo 5.2.0"><link rel="alternate" href="/atom.xml" title="JohnnyRian NameSpace" type="application/atom+xml">
</head><body><header class="feature-header"><nav class="component-nav"><ul><div class="logo-container"><a href="/"><h2 class="title">JohnnyRian NameSpace</h2></a></div><a href="/" target="_self" class="li component-nav-item"><p>INDEX</p></a><a href="/archives" target="_self" class="li component-nav-item"><p>ARCHIVES</p></a><ul class="shortcut-icons"><a href="https://github.com/Renleilei1992" target="_blank"><img src="/images/github.svg" class="icon"></a><a href="/atom.xml" target="_blank"><img src="/images/rss.svg" class="icon"></a><a href="https://www.zhihu.com/people/ren-lei-lei-99" target="_blank"><img src="/images/zhihu.svg" class="icon"></a></ul></ul></nav></header><main class="container"><div id="post-container"><div class="post"><article class="post-block"><h1 class="post-title">锁的实现(PV原语)</h1><div class="post-info">2020年12月17日</div><div class="post-content"><hr>
<h2 id="锁与PV原语"><a href="#锁与PV原语" class="headerlink" title="锁与PV原语"></a>锁与PV原语</h2><hr>
<h4 id="锁"><a href="#锁" class="headerlink" title="锁"></a>锁</h4><ul>
<li><p><strong>应用场景-为什么需要锁</strong>：</p>
<ul>
<li>多线程访问共享的资源时, 为了避免资源竞争导致数据错乱, 我们需要在访问共享资源之前加锁, 此处的锁是一个系统级别的资源, 是需要线程去找系统获取, 对此系统级的资源的操作为原语操作.</li>
<li>一个小例子演示多线程情况下共享资源的数据错乱问题. <a target="_blank" rel="noopener" href="https://github.com/Renleilei1992/MyCplusplus/blob/master/Main/251_test_multiThread.cpp">251_test_multiThread.cpp</a></li>
<li><img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/2%E7%BA%BF%E7%A8%8B%E8%8E%B7%E5%8F%96%E9%94%81.png" width = "421" height = "496"></li>
</ul>
</li>
<li><p><strong>本质-锁是什么</strong>：</p>
<ul>
<li>我理解下来是 在计算机里本质上就是一块内存空间。当这个空间被赋值为1的时候表示加锁了，被赋值为0的时候表示解锁了。多个线程争抢一个锁，就是抢着要把这块内存赋值为1。在一个多核环境里，内存空间是共享的。每个核上各跑一个线程，那如何保证一次只有一个线程成功抢到锁呢？</li>
</ul>
</li>
<li><p><strong>锁在硬件层面的实现</strong>：</p>
<ul>
<li>CPU提供一些用来构建锁的Atomic原子指令，例如X86的 CMPXCHG （加上LOCK前缀）<code>__asm__ volatile (LOCK_IF_MP(%4) &quot;cmpxchgl %1,(%3)&quot;</code>, 能够完成Atomic的 compare-and-swap(CAS),使用这种硬件指令就能实现spinlock自旋锁.</li>
<li>LOCK前缀的的作用是锁定系统总线（或者锁定某一块cache line）来实现atomicity，可以了解下基础的缓存一致协议譬如MSEI。简单来说就是，如果指令前加了LOCK前缀，就是告诉其他核，一旦我开始执行这个指令了，在我结束这个指令之前，谁也不许动。缓存一致协议在这里面扮演了重要角色，这里先不赘述。这样便实现了一次只能有一个核对同一个内存地址赋值。</li>
<li>CPU通过对锁总线、锁北桥信号或者锁cache line的方式来解决多核同时获取资源的情况<ul>
<li><blockquote>
<p>在cpu芯片上有一个HLOCK Pin，可以通过发送指令来操作，将#HLOCK Pin电位拉低，并持续到这条指令执行完毕，从而将总线锁住，这样同一总线上的其他CPU就不能通过总线来访问内存。最开始这些功能是用来测试cpu的，后来被操作系统实现而封装成各种功能：关键代码段，信号量等</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>锁与操作系统之间的关系</strong><a target="_blank" rel="noopener" href="https://www.zhihu.com/question/332113890/answer/1052024052">操作系统具体是怎么实现锁的</a></p>
<ul>
<li><blockquote>
<p>一个spin lock就是让没有抢到锁的线程不断在while里面循环进行compare-and-swap，燃烧CPU，浪费青春，直到前面的线程放手（对应的内存被赋值0）。这个过程不需要操作系统的介入，这是运行程序和硬件之间的故事。如果需要长时间的等待，这样反复CAS轮询就比较浪费资源，这个时候程序可以向操作系统申请被挂起，然后持锁的线程解锁了以后再通知它。这样CPU就可以用来做别的事情，而不是反复轮询。但是OS切换线程也需要一些开销，所以是否选择被挂起，取决于大概是否需要等很长时间，如果需要，则适合挂起切换为别的线程。（偏向锁，比如设置自旋10次挂起等策略）</p>
</blockquote>
</li>
<li><blockquote>
<p>进程向操作系统请求被挂起是通过一个系统调用，在linux上的实现就是futex，宏观来讲，OS需要一些全局的数据结构来记录一个被挂起进程和对应的锁的映射关系，这样一个数据结构天然是全局的，因为多个OS进程可能同时操作它。所以，实现高效的锁本身也需要锁。</p>
</blockquote>
</li>
</ul>
</li>
<li><p>补充<strong>Futex</strong>(快速用户空间互斥体) <a target="_blank" rel="noopener" href="https://blog.csdn.net/ixidof/article/details/42174871">一个多线程程序挂起问题解决-含有Futex的解读</a></p>
<ul>
<li>概念: Futex是一种用户态和内核态混合的同步机制;</li>
<li>作用: futex就是通过在用户态的检查，如果没有竞争就不用陷入内核去执行系统调用，大大提高了低竞争时候的效率</li>
<li>解决的问题: <ul>
<li><blockquote>
<p>经研究发现，很多同步是无竞争的，即某个进程进入互斥区，到再从某个互斥区出来这段时间，常常是没有进程也要进这个互斥区或者请求同一同步变量的。但是在这种情况下，这个进程也要陷入内核去看看有没有人和它竞争，退出的时侯还要陷入内核去看看有没有进程等待在同一同步变量上。这些不必要的系统调用(或者说内核陷入)造成了大量的性能开销。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>锁的不同类型</strong><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/CqIXHowIDT1kxyBOO0x7TQ">互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景</a>：</p>
<ul>
<li>互斥锁：一种同时只允许一个进程访问的资源；</li>
<li>自旋锁：一种不断while循环获取资源的方式,很耗费CPU资源；</li>
<li>读写锁：一种同时允许多个访问，但是只允许单个修改的资源；</li>
<li>乐观锁</li>
<li>悲观锁</li>
</ul>
</li>
<li><p>分类为：互斥锁mutex、自旋锁spinlock、读写锁rwlock</p>
</li>
</ul>
<hr>
<h4 id="PV原语"><a href="#PV原语" class="headerlink" title="PV原语"></a>PV原语</h4><ul>
<li><p><strong>原语是什么?</strong></p>
<ul>
<li><p>提到<strong>原语</strong>(primitive)这个概念，需要先了解操作系统内核状态中的原语概念. <a target="_blank" rel="noopener" href="https://baike.baidu.com/item/%E5%8E%9F%E8%AF%AD/3794081">百度百科-原语</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_36879493/article/details/107856883">操作系统的运行机制</a></p>
</li>
<li><p>操作系统的内核是指在计算机硬件上扩充的第一层软件。通常是一些与硬件紧密相关的模块。内核的实现是通过<strong>原语</strong>实现的，而原语又是由原子操作构成的。</p>
</li>
<li><blockquote>
<p>CPU的状态划分为用户态和内核态。CPU处于内核态可以执行特权指令，CPU处于用户态，只能执行非特权指令；区分这两个状态的原因是需要保护操作系统，比如保护PCB（进程控制块 Process Control Block：进程实体的一部分，操作系统用它来描述一个进程实体）<img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/%E7%94%A8%E6%88%B7%E6%80%81-%E5%86%85%E6%A0%B8%E6%80%81%E5%88%87%E6%8D%A2.png" alt="用户态-内核态切换"></p>
</blockquote>
</li>
<li><p>大多数操作系统内核(内核态)包括四个方面 <a target="_blank" rel="noopener" href="https://blog.csdn.net/redRnt/article/details/83050980">操作系统——用户态与内核态</a></p>
<ul>
<li>1, 时钟管理</li>
<li>2, 中断处理</li>
</ul>
<hr>
<ul>
<li><p>3, <strong>原语操作</strong></p>
<ul>
<li><blockquote>
<p>按层次结构设计的操作系统，底层必然是一些可被调用的公共小程序，它们各自完成一个规定的操作.</p>
</blockquote>
</li>
<li><p>处于操作系统的最底层，是最接近硬件的部分；</p>
</li>
<li><p>这些程序的运行具有原子性，其操作只能一气呵成，不可被中断；</p>
</li>
<li><p>这些程序的运行时间都很短，而且调用频繁。</p>
</li>
<li><p>比如<a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000022099460">重学操作系统-进程与线程</a>: </p>
<ul>
<li>创建进程原语：Create()</li>
<li>终止进程原语：Terminate()</li>
<li>阻塞进程原语：Block()</li>
<li>唤醒进程原语：Wakeup()</li>
<li>挂起进程原语：Suspend()</li>
<li>激活进程原语：Activate()</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>4, 系统控制的数据结构及处理<ul>
<li>进程管理</li>
<li>存储器管理</li>
<li>设备管理</li>
</ul>
</li>
</ul>
</li>
<li><blockquote>
<p><strong>原语</strong>是由若干指令构成的，用于完成一定功能的过程。而原子操作是指在这过程中的行为 ，要么全做，要么不做，不可中断（以保证操作的完整性）。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><p><strong>原语的用途</strong> &gt; 进程的阻塞与唤醒</p>
<ul>
<li><blockquote>
<p>引进原语的主要目的是为了实现进程的通信和控制, 原语是操作系统核心（不是由进程，而是由一组程序模块组成）的一个组成部分，并且常驻内存，通常在管态下执行。原语一旦开始执行，就要连续执行完，不允许中断</p>
</blockquote>
</li>
<li><blockquote>
<p>进程执行过程中常常会因为等待I/O或者某个事件、条件的出现而进入阻塞状态。当处于阻塞状态的进程所等待的操作完成或者事件出现、条件满足时，进程就会从阻塞状态唤醒而进入就绪状态。因此进程的阻塞是进程自身的一种主动行为，是进程自己通过阻塞原语自己阻塞。</p>
</blockquote>
</li>
<li><p>进程的阻塞原语Block():</p>
<ul>
<li>停止进程执行</li>
<li>进程插入阻塞队列</li>
<li>重新调度</li>
</ul>
</li>
<li><p>进程的唤醒原语Wakeup():</p>
<ul>
<li>将进程从阻塞队列卸下</li>
<li>插入就绪队列</li>
<li>修改进程在PCB中的状态</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>原语的使用</strong>:</p>
<ul>
<li>异步原语</li>
<li>进程控制块(PCB)的原语<ul>
<li>创建原语</li>
<li>撤销原语</li>
<li>阻塞原语 P</li>
<li>唤醒原语 V</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><p><strong>PV原语</strong></p>
<ul>
<li><blockquote>
<p>PV原语通过操作信号量来处理进程间的同步与互斥的问题, 其核心就是一段不可分割不可中断的程序。提及PV原语就不得不介绍一个概念-信号量.<br>信号量的概念1965年由著名的荷兰计算机科学家Dijkstra提出，其基本思路是用一种新的变量类型(semaphore)来记录当前可用资源的数量。</p>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li><p>Semaphore的类型:</p>
<ul>
<li>1, 计数信号量, 一个整型数字;</li>
<li>2, 记录型信号量, 一个整型数字+一个阻塞在此资源上的进程队列(PCB队列);<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">structure semaphore</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">int</span> value; （整型变量）</span><br><span class="line">    pointer PCB <span class="built_in">queue</span>; （进程队列）</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>3, 二进制信号量, 对应Linux上的Mutex互斥锁;</li>
</ul>
</li>
<li><p>Semaphore有两种使用方式：</p>
<ol>
<li>semaphore的取值必须大于或等于0，0表示当前已没有空闲资源，而正数表示当前空闲资源的数量；</li>
<li>semaphore的取值可正可负，负数的绝对值表示正在等待进入临界区的进程个数。</li>
</ol>
<ul>
<li><blockquote>
<p>信号量是由操作系统来维护的，用户进程只能通过初始化和两个标准原语(P、V原语)来访问。初始化可指定一个非负整数，即空闲资源总数。</p>
</blockquote>
</li>
</ul>
</li>
<li><p>Semaphore 信号量的操作有如下几种:</p>
<ol>
<li>初始化: 给与它一个非负数的整数值;</li>
<li>P操作: 以阻塞原语的方式实现, 信号量的值仅能由这两条原语来改变;</li>
<li>V操作: 以唤醒原语的方式实现;</li>
</ol>
</li>
<li><p>P原语: 阻塞原语 (wait()负责把当前进程由运行状态转换为阻塞状态，直到另外一个进程唤醒它)</p>
<ul>
<li><blockquote>
<p>操作为：申请一个资源(把该资源的信号量减1)，若成功，则申请成功；若失败，则该进程被阻塞；</p>
</blockquote>
</li>
<li><p>P原语操作的动作是：</p>
<ul>
<li>(1)sem减1；</li>
<li>(2)若sem减1后仍大于或等于零，则P原语返回，该进程继续执行；</li>
<li>(3)若sem减1后小于零，则该进程被阻塞后进入与该信号相对应的队列中，然后转进程调度。</li>
</ul>
</li>
</ul>
</li>
<li><p>V原语: 唤醒原语 (signal()负责把一个被阻塞的进程唤醒，它有一个参数表，存放着等待被唤醒的进程信息)</p>
<ul>
<li><blockquote>
<p>操作为：释放一个被占用的资源(把信号量加1)，如果发现有被阻塞的进程，则选择一个唤醒之。</p>
</blockquote>
</li>
<li><p>V原语操作的动作是：</p>
<ul>
<li>(1)sem加1；</li>
<li>(2)若相加结果大于零，则V原语停止执行，该进程返回调用处，继续执行；</li>
<li>(3)若相加结果小于或等于零，则从该信号的等待队列中唤醒一等待进程，然后再返回原进程继续执行或转进程调度。</li>
<li><blockquote>
<p>PV操作对于每一个进程来说，都只能进行一次，而且必须成对使用。在PV原语执行期间不允许有中断的发生。</p>
</blockquote>
</li>
</ul>
</li>
<li><p>PV原语操作示意图 <img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/PV%E5%8E%9F%E8%AF%AD%E6%93%8D%E4%BD%9C01.jpg" alt="PV原语01"></p>
</li>
</ul>
<hr>
<ul>
<li>两个进程涉及PV操作示意图<img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/PV%E5%8E%9F%E8%AF%AD%E6%93%8D%E4%BD%9C02.jpg" alt="PV原语02"></li>
</ul>
<hr>
<ul>
<li>多个进程涉及PV操作示意图<img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/%E5%A4%9A%E7%BA%BF%E7%A8%8BPV%E9%98%BB%E5%A1%9E01.png" alt="PV操作01"></li>
</ul>
</li>
<li><p>简单操作示例 <a target="_blank" rel="noopener" href="https://www.cnblogs.com/xzh31390080/p/4390058.html">PV原语操作详解-一些pv操作的简单示例</a>:</p>
<ul>
<li>1, 某超市门口为顾客准备了100辆手推车，每位顾客在进去买东西时取一辆推车，在买完东西结完帐以后再把推车还回去  <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">semaphore S_CartNum;    <span class="comment">// 空闲的手推车数量，初值为100</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">consumer</span><span class="params">(<span class="keyword">void</span>)</span>     <span class="comment">// 顾客进程</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    P(S_CartNum);       <span class="comment">// 阻塞一辆手推车 S_cartNum - 1</span></span><br><span class="line">    <span class="comment">// do something cost time!</span></span><br><span class="line">    V(S_CartNum);       <span class="comment">// 归还一辆手推车 S_cartNum + 1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>2, 餐桌只有20桌，来吃饭的人有30桌人，先来的20桌人进去占满了位置，剩余的客人拿号。  <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">semaphore S_tableNum;   <span class="comment">// 桌子数量 20</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">comsumer</span><span class="params">(<span class="keyword">void</span>)</span>     <span class="comment">// 顾客进程</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    P(S_tableNum);</span><br><span class="line">    <span class="comment">// eating cost time.</span></span><br><span class="line">    V(S_tableNum);      <span class="comment">// 吃完结账后空出一个桌子，但是有10桌人等号，sem+1 = -9, 此时唤醒队列中排队的 001 号.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
</li>
</ul>
<h4 id="进程调度"><a href="#进程调度" class="headerlink" title="进程调度"></a>进程调度</h4><ul>
<li><p>进程三态: 等待、就绪、运行 <img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/%E8%BF%9B%E7%A8%8B3%E6%80%8102.jpg" alt="进程调度"></p>
</li>
<li><p>从操作系统进程调度的视角看:</p>
<ul>
<li>1, 进程调度会把“<strong>缺乏资源</strong>”的进程置于阻塞队列;</li>
<li>2, 把阻塞队列中资源要求可以满足的进程置于就绪队列;</li>
<li>3, 然后时间片一到就会对就绪队列进行调度运行.</li>
<li>这里的资源不仅是我们熟知的磁盘访问、网络通信等，操作系统对其进行 <strong><em>归一化</em></strong> 处理，都统一维护到一个类似资源表的结构中。</li>
<li>每个系统维护的资源都有记录一些相关的信息，比如正在申请该资源的进程队列（例如借助一些工具能够看到当前有哪些进程正在试图访问磁盘）</li>
</ul>
</li>
<li><p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzUxODAzNDg4NQ==&mid=2247485264&idx=1&sn=78585cbabd1e0c333b43a3abd2b2ff64&scene=21#wechat_redirect">多个线程为了同个资源打起架来了，该如何让他们安分？</a></p>
</li>
<li><blockquote>
<p>在单核 CPU 系统里，为了实现多个程序同时运行的假象，操作系统通常以时间片调度的方式，让每个进程执行每次执行一个时间片，时间片用完了，就切换下一个进程运行，由于这个时间片的时间很短，于是就造成了「并发」的现象。</p>
</blockquote>
</li>
<li><blockquote>
<p>另外，操作系统也为每个进程创建巨大、私有的虚拟内存的假象，这种地址空间的抽象让每个程序好像拥有自己的内存，而实际上操作系统在背后秘密地让多个地址空间「复用」物理内存或者磁盘<img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86-%E9%A1%B5%E8%A1%A8.png" width = "540" height = "329"></p>
</blockquote>
</li>
<li><blockquote>
<p>如果一个程序只有一个执行流程，也代表它是单线程的。当然一个程序可以有多个执行流程，也就是所谓的多线程程序，线程是调度的基本单位，进程则是资源分配的基本单位。所以，线程之间是可以共享进程的资源，比如代码段、堆空间、数据段、打开的文件等资源，但每个线程都有自己独立的栈空间。</p>
</blockquote>
</li>
<li><blockquote>
<p>那么问题就来了，多个线程如果竞争共享资源，如果不采取有效的措施，则会造成共享数据的混乱。</p>
</blockquote>
</li>
</ul>
<hr>
<h4 id="一些术语回顾"><a href="#一些术语回顾" class="headerlink" title="一些术语回顾"></a>一些术语回顾</h4><ul>
<li><p><strong>北桥信号</strong>(PCI桥-高速设备): 连接CPU和内存、显卡之间的通信;</p>
</li>
<li><p><strong>南桥信号</strong>(ISA桥-低速设备): 磁盘IDE总线、打印机、声卡等;</p>
</li>
<li><p><strong>总线</strong>: 芯片组控制了数据传输的流转.</p>
</li>
<li><p><strong>cache\cacheLine</strong> <a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/31422201">cache探秘</a>: <img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%90%84%E7%BA%A7%E5%AD%98%E5%82%A8%E5%8F%8A%E9%80%9F%E5%BA%A6.jpg" alt="各级存储"></p>
</li>
<li><p><strong>系统延时</strong><img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%90%84%E7%A7%8D%E5%BB%B6%E6%97%B6.png" alt="系统的各种延时"></p>
</li>
<li><p><strong>CPU</strong><img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86%E5%99%A8-CPU%E7%95%8C%E9%9D%A2.jpg" alt="任务管理器-CPU"></p>
</li>
<li><p>Intel最新的搭载Iris锐炬集成显卡的CPU(L4缓存也可以作为显存使用)<img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/eDRAM-Iris%E8%8A%AF%E7%89%87%E7%BC%93%E5%AD%98%E6%9E%B6%E6%9E%84.jpg" alt="Iris-eDRAM架构图"></p>
</li>
<li><p><strong>进程控制块PCB</strong>：操作系统管理控制进程运行所用的信息集合 <a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/88017448">操作系统—进程管理</a></p>
<ul>
<li>PCB是用来描述进程的数据结构。操作系统为每个进程都维护了一个PCB，用来保存与该进程有关的各种状态信息;</li>
<li>PCB中存储的信息: 1, 进程标识信息; 2, 处理机状态信息保存区; 3, 进程控制信息;</li>
<li>PCB组织方式: 1.链表：同一状态的进程其PCB成一链表，多个状态对应多个不同的链表。如就绪链表，阻塞链表；2，索引表：同一状态的进程归入一个index表，多个状态对应多个不同的index表。如就绪索引表，阻塞索引表。</li>
<li>上下文切换: 寄存器（PC，SP等）CPU状态。进程切换的过程是将被切换的进程会将信息保存在PCB中，把另一个进程中的PCB信息恢复到CPU中</li>
</ul>
</li>
<li><p><strong>进程与线程</strong> <a target="_blank" rel="noopener" href="https://www.cnblogs.com/kevingrace/p/5252919.html">如何查询一个进程下面的线程数（进程和线程区别）</a></p>
<ul>
<li><p>1）两者概念</p>
<ul>
<li>进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.</li>
<li>线程是指进程内的一个执行单元,也是进程内的可调度实体. 线程是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.</li>
</ul>
</li>
<li><p>2）两者关系</p>
<ul>
<li>一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.</li>
<li>相对进程而言，线程是一个更加接近于执行体的概念，它可以与同进程中的其他线程共享数据，但拥有自己的栈空间，拥有独立的执行序列。</li>
</ul>
</li>
<li><p>3）两者区别</p>
<ul>
<li><p>进程和线程的主要差别在于它们是不同的操作系统资源管理方式：进程有独立的地址空间，一个进程崩溃后，在保护模式下不会对其它进程产生影响；而线程只是一个进程中的不同执行路径。</p>
</li>
<li><p>线程有自己的堆栈和局部变量，但线程之间没有单独的地址空间，一个线程死掉就等于整个进程死掉，所以多进程的程序要比多线程的程序健壮，但在进程切换时，耗费资源较大，效率要差一些. 但对于一些要求同时进行并且又要共享某些变量的并发操作，只能用线程，不能用进程。</p>
</li>
<li><p><strong>进程和线程的区别</strong></p>
<ul>
<li>地址空间:线程是进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;</li>
<li>资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源</li>
<li>线程是处理器调度的基本单位,但进程不是.</li>
<li>进程和线程二者均可并发执行.</li>
<li>简而言之,一个程序至少有一个进程,一个进程至少有一个线程.</li>
<li>线程的划分尺度小于进程，使得多线程程序的并发性高。</li>
<li>另外，进程在执行过程中拥有独立的内存单元，而多个线程共享内存，从而极大地提高了程序的运行效率。</li>
<li>线程在执行过程中与进程是有区别的。每个独立的线程有一个程序运行入口、顺序执行序列和程序的出口。但是线程不能够独立执行，必须依存在应用程序中，由应用程序提供多个线程执行控制。</li>
<li>从逻辑角度来看，多线程的意义在于一个应用程序中，有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用，来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。</li>
</ul>
</li>
</ul>
</li>
<li><p>4）优缺点 线程和进程在使用上各有优缺点：</p>
<ul>
<li>线程执行开销小，但不利于资源的管理和保护；而进程正相反。</li>
<li>线程适合于在SMP机器上（即对称多处理结构的简称,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构）运行，而进程则可以跨机器迁移。</li>
</ul>
</li>
<li><p>5）一些关于进程线程的常用Linxu shell命令</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ps -ef | grep XXXX      // 查找某个进程的pid进程号</span><br><span class="line">pstree -p $pid          // 查看某个进程下有多少个线程</span><br><span class="line">top -Hp $pid            // 具体查看该进程下的多个线程情况</span><br><span class="line">ps hH p $pid | wc -l    // 直接查看某个进程下有多少个线程</span><br></pre></td></tr></table></figure>

</li>
</ul>
</li>
</ul>
<hr>
<h3 id="杂记"><a href="#杂记" class="headerlink" title="杂记"></a>杂记</h3><hr>
<h4 id="虚拟机-与-非虚拟机中的线程管理"><a href="#虚拟机-与-非虚拟机中的线程管理" class="headerlink" title="虚拟机 与 非虚拟机中的线程管理"></a>虚拟机 与 非虚拟机中的线程管理</h4><h5 id="自己管理线程的语言"><a href="#自己管理线程的语言" class="headerlink" title="自己管理线程的语言"></a>自己管理线程的语言</h5><ul>
<li><p>比如Java的JVM虚拟机，使用时 new Thread() 调用start() -&gt; JVM 空间的线程对象 -&gt; OS 空间 kernel thread对象  比例对象是 1:1</p>
<ul>
<li>比较重</li>
<li>synchronized 上锁</li>
<li>示例上下文: ++<strong>多个线程对同一个变量进行+1操作</strong>++.<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// synchronized 重量级锁</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CasAndUnsafe_01</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span>  <span class="keyword">int</span> m = <span class="number">0</span>;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        Thread[] threads = <span class="keyword">new</span> Thread[<span class="number">100</span>];</span><br><span class="line">        <span class="keyword">final</span> CoutDownLatch latch = <span class="keyword">new</span> CoutDownLatch(threads.length);</span><br><span class="line">        </span><br><span class="line">        Object o = <span class="keyword">new</span> Object();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; threads.length; i++)&#123;</span><br><span class="line">            threads[i] = <span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">                <span class="keyword">synchronized</span> (o)&#123;</span><br><span class="line">                    <span class="keyword">for</span>(<span class="keyword">int</span> j = <span class="number">0</span>; j &lt;<span class="number">10000</span>; j++)&#123;</span><br><span class="line">                        m++;</span><br><span class="line">                    &#125;</span><br><span class="line">                    latch.countDown();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        Arrays.stream(threads).forEach((t)-&gt; t.start());</span><br><span class="line">        latch.await();</span><br><span class="line">        System.out.println(m);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// AtomicInteger 轻量级锁 无锁 自旋锁</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AtomicInteger_01</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> AtomicInteger m = <span class="keyword">new</span> AtomicInteger(<span class="number">0</span>);</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line"> </span><br><span class="line">        Thread[] threads = <span class="keyword">new</span> Thread[<span class="number">100</span>];</span><br><span class="line"> </span><br><span class="line">        CountDownLatch latch = <span class="keyword">new</span> CountDownLatch(threads.length);</span><br><span class="line"> </span><br><span class="line">        Object o = <span class="keyword">new</span> Object();</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; threads.length; i++)&#123;</span><br><span class="line">            threads[i] = <span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">                <span class="keyword">for</span>(<span class="keyword">int</span> j = <span class="number">0</span>; j &lt; <span class="number">10000</span>; j++)&#123;</span><br><span class="line">                    <span class="comment">//m++</span></span><br><span class="line">                    m.incrementAndGet();        <span class="comment">// 看起来没有加锁</span></span><br><span class="line">                &#125;</span><br><span class="line">                latch.countDown();</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        Arrays.stream(threads).forEach((t)-&gt; t.start());</span><br><span class="line">        latch.await();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<hr>
<ul>
<li><p>重量级锁 ：JDK1.2版本之前的锁实现都是jvm自己不关心，交给操作系统来处理线程和锁的结果(之后的JDK版本会不同程度更新synchronized, 引入锁升级、偏向锁的概念)</p>
<ul>
<li><blockquote>
<p>系统维护一个资源的队列，队列中是等待wait此资源的对象(进程)，此资源被释放时唤醒队列中的等待者</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><p>轻量级锁 ：JVM空间对线程和锁自行管理，不通过操作系统的锁来实现  – 自旋锁 (AtomicInteger -&gt; incrementAndGet() -&gt; compareAndAddInt() native c++方法内部会有锁 -&gt; CAS compare and swap\set\exchange )</p>
<ul>
<li><blockquote>
<p>每个等待者都是while循环在争抢这个资源，一直消耗CPU资源。</p>
</blockquote>
</li>
<li><p>CAS 原理 乐观自旋锁概念：当写回该变量的时候会compare原始的值是否与当前该变量的值相同，相同则写入，不同则替换原始的值并重复操作后再compare，while循环直到原始值与当前该变量的值相等为止，但是没有使用sync锁；</p>
</li>
<li><p>1, ABA 问题：0(原始值)-&gt;1(线程A改为1)-&gt;0(线程B改为0)，在使用乐观自旋的时候compare原始值发现一致，但是其实中途经历过被改为3，处理此问题可以加入版本号来处理，bool型或者时间戳型；</p>
</li>
<li><p>2, 原子性：在compare的过程中 </p>
<ul>
<li><code>if(n == 0) n++;</code> 这个过程也必须要保证其原子性</li>
<li>unsafe.cpp -&gt; CompareAndSwapInt() -&gt; Atomic::cmpxchg -&gt; linux_x86 实现: LOCK_IF_MP</li>
</ul>
</li>
<li><p>Atomic::cmpxchg 内部实现：</p>
<ul>
<li>1, 判断当前系统是否为多核cpu: 1)是，汇编语句前 + Lock；2)否，直接执行汇编语句 cmpxchg;</li>
<li>2, 其中lock指令是怎么工作的呢？lock指令在执行后面的语句时会锁定一个北桥信号(锁总线与锁北桥，硬件级别的锁, 又有信息提到是只有在 cache line lock 行锁不生效的时候才会使用 bus lock,bus lock对性能影响大，锁住了内存的bus)；</li>
<li>3, 例如：cpu1 和 cpu2 同时通过总线访问内存中的一个变量a，cpu1拿到a的原始值是0，做+1处理后写回到内存中，先做判断，原始值是否和当前内存中的值相同，相同，则将+1处理后的结果写入到内存中，若原始值与当前内存中的a值不一样，则拿当前内存中的a值回到cpu中进行+1运算后再次写回到内存中；</li>
<li>4, <strong>asm</strong>  assembly 汇编；</li>
<li>5, 轻量级锁(自旋锁)与重量级锁(经过操作系统申请,内核态调用,并返回一个锁对象),并没有效率一定谁优于谁，不同应用场景.</li>
<li>6, lock的作用: 在多处理器中执行指令时对共享内存独占使用；能够立即将当前处理器对应的缓存的内容刷新到内存，并使其他处理器对应的缓存失效；<br>提供了有序指令无法超过这个内存屏障的作用</li>
</ul>
</li>
<li><p><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/126384164">从源码看CAS</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_53273362/article/details/110825492">CAS与Atomic类</a></p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// atomic_linux_x86.inline.hpp</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> jint  <span class="title">Atomic::cmpxchg</span><span class="params">(jint exchange_value, <span class="keyword">volatile</span> jint* dest, jint  compare_value)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">int</span> mp = os::is_MP();</span><br><span class="line">  <span class="function">__asm__ <span class="title">volatile</span> <span class="params">(LOCK_IF_MP(%<span class="number">4</span>) <span class="string">&quot;cmpxchgl %1,(%3)&quot;</span></span></span></span><br><span class="line"><span class="function"><span class="params">                    : <span class="string">&quot;=a&quot;</span> (exchange_value)</span></span></span><br><span class="line"><span class="function"><span class="params">                    : <span class="string">&quot;r&quot;</span> (exchange_value), <span class="string">&quot;a&quot;</span> (compare_value), <span class="string">&quot;r&quot;</span> (dest), <span class="string">&quot;r&quot;</span> (mp)</span></span></span><br><span class="line"><span class="function"><span class="params">                    : <span class="string">&quot;cc&quot;</span>, <span class="string">&quot;memory&quot;</span>)</span></span>;</span><br><span class="line">  <span class="keyword">return</span> exchange_value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// LOCK_IF_MP 定义</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOCK_IF_MP(mp) <span class="meta-string">&quot;cmp $0, &quot;</span> #mp <span class="meta-string">&quot;; je 1f; lock; 1: &quot;</span></span></span><br></pre></td></tr></table></figure>
<ul>
<li>总结: CAS不是无锁, 内部实现仍旧会使用硬件级别的锁. lock cmpxchg.</li>
</ul>
</li>
</ul>
<hr>
</li>
<li><p>重量级锁在线程数多或者任务耗时长的时候使用, 轻量级锁如果任务多或者线程数多会非常消耗CPU资源.</p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://blog.csdn.net/cjl836735455/article/details/106745984/">JAVA 多线程与高并发 – 很详细, 总结自马士兵的公开课</a></p>
</li>
</ul>
</li>
<li><p>Golang 协程 对象比是 M:N (M&gt;=N)</p>
<ul>
<li>内核对象中会有多个线程实例，单个线程实例中会有golang的多个队列协程  对应Java中的 ForkJoin 线程池</li>
</ul>
</li>
<li><p>C++ 的 CAS 原语</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/56055215">c++并发编程3. CAS原语</a></li>
<li>C++11中的Atomic <a target="_blank" rel="noopener" href="https://www.cnblogs.com/muhe221/articles/5089918.html">C++11 CAS无锁函数compare_exchange_weak的使用</a><ul>
<li>compare_exchange_weak </li>
<li>compare_exchange_strong</li>
</ul>
</li>
<li>GCC4.1.2中出现的比如__sync_add_and_fetch 的 __sync_* 函数.</li>
<li>CAS原语有三个参数，内存地址，期望值，新值 <a target="_blank" rel="noopener" href="https://blog.csdn.net/chinabhlt/article/details/46618405">CAS无锁操作</a><ul>
<li>如果内存地址中的值==期望值,表示该值未修改,此时可以修改为新值.</li>
<li>否则修改失败返回false.</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><p>Atomic硬件实现原理 <a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/115355303">atomic实现原理</a></p>
<ul>
<li><blockquote>
<p>问题背景: increase一个变量，CPU微观指令级别分成3步操作。1) 先read变量的值到CPU内存寄存器；2) 对寄存器的值递增；3) 将寄存器的值写回变量；在两个CPU的系统中都执行increase函数, 可能出现:</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">   +    +----------------------+----------------------+</span><br><span class="line">   |    |    CPU0 operation    |    CPU1 operation    |</span><br><span class="line">   |    +----------------------+----------------------+</span><br><span class="line">   |    | read counter (&#x3D;&#x3D; 0)  |                      |</span><br><span class="line">   |    +----------------------+----------------------+</span><br><span class="line">   |    |       increase       | read counter (&#x3D;&#x3D; 0)  |</span><br><span class="line">   |    +----------------------+----------------------+</span><br><span class="line">   |    | write counter (&#x3D;&#x3D; 1) |       increase       |</span><br><span class="line">   |    +----------------------+----------------------+</span><br><span class="line">   |    |                      | write counter (&#x3D;&#x3D; 1) |</span><br><span class="line">   |    +----------------------+----------------------+</span><br><span class="line">   V</span><br><span class="line">timeline</span><br></pre></td></tr></table></figure></li>
<li><p>硬件如何实现原子操作期间不被打断</p>
<ul>
<li><p>Bus Lock （锁总线或者锁北桥）</p>
<ul>
<li><blockquote>
<p>当CPU发出一个原子操作时，可以先锁住Bus（总线）。这样就可以防止其他CPU的内存操作。等原子操作结束，释放Bus。这样后续的内存操作就可以进行。这个方法可以实现原子操作，但是锁住Bus会导致后续无关内存操作都不能继续。实际上，我们只关心我们操作的地址数据。只要我们操作的地址锁住即可，而其他无关的地址数据访问依然可以继续</p>
</blockquote>
</li>
</ul>
</li>
<li><p>Cacheline Lock （锁缓存行）</p>
<ul>
<li><blockquote>
<p>当CPU0试图执行原子递增操作时。a) CPU0发出”Read Invalidate”消息，其他CPU将原子变量所在的缓存无效，并从Cache返回数据。CPU0将Cache line置成Exclusive状态。然后将该cache line标记locked。b) 然后CPU0读取原子变量，修改，最后写入cache line。c) 将cache line置位unlocked。</p>
</blockquote>
</li>
<li><blockquote>
<p>在步骤a)和c)之间，如果其他CPU（例如CPU1）尝试执行一个原子递增操作，CPU1会发送一个”Read Invalidate”消息，CPU0收到消息后，检查对应的cache line的状态是locked，暂时不回复消息（CPU1会一直等待CPU0回复Invalidate Acknowledge消息）。直到cache line变成unlocked。这样就可以实现原子操作。我们称这种方式为锁cache line。这种实现方式必须要求操作的变量位于一个cache line</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h5 id="操作系统线程对应关系"><a href="#操作系统线程对应关系" class="headerlink" title="操作系统线程对应关系"></a>操作系统线程对应关系</h5><hr>
<h5 id="自旋锁-Spinlock-与-互斥锁-Mutex-区别"><a href="#自旋锁-Spinlock-与-互斥锁-Mutex-区别" class="headerlink" title="自旋锁 Spinlock 与 互斥锁 Mutex 区别"></a>自旋锁 Spinlock 与 互斥锁 Mutex 区别</h5><ul>
<li><blockquote>
<p>POSIX threads(简称pthreads，linux多线程)是在多核平台上进行并行编程的一套常用的API。<strong>线程同步(Thread Synchronization)**是并发编程中非常重要的手段，其中最典型的应用就是用pthreads提供的锁机制(lock)来对多个线程之间共享的临界区(**Critical Section</strong>：++每个线程中访问临界资源的那段代码，不论是硬件临界资源，还是软件临界资源++)进行保护(另一种常用的同步机制是barrier)</p>
</blockquote>
</li>
<li><p>Pthreads提供了多种锁机制：</p>
<ul>
<li>(1) Mutex（互斥量）：pthread_mutex_***</li>
<li>(2) Spin lock（自旋锁）：pthread_spin_***</li>
<li>(3) Condition Variable（条件变量）：pthread_con_***</li>
<li>(4) Read/Write lock（读写锁）：pthread_rwlock_***</li>
</ul>
</li>
<li><p><strong>SpinLock</strong> 与 <strong>Mutex</strong> 是两种不同的锁实现：</p>
<ul>
<li>pthreads提供的<strong>Mutex</strong>锁操作相关的API主要有：<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pthread_mutex_lock (<span class="keyword">pthread_mutex_t</span> *mutex);</span><br><span class="line">pthread_mutex_trylock (<span class="keyword">pthread_mutex_t</span> *mutex);</span><br><span class="line">pthread_mutex_unlock (<span class="keyword">pthread_mutex_t</span> *mutex);</span><br></pre></td></tr></table></figure>
</li>
</ul>
<hr>
<ul>
<li><p>pthreads提供的与<strong>Spin Lock</strong>锁操作相关的API主要有：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pthread_spin_lock (<span class="keyword">pthread_spinlock_t</span> *lock);</span><br><span class="line">pthread_spin_trylock (<span class="keyword">pthread_spinlock_t</span> *lock);</span><br><span class="line">pthread_spin_unlock (<span class="keyword">pthread_spinlock_t</span> *lock);</span><br></pre></td></tr></table></figure>
</li>
<li><p>Mutex: 属于 sleep-waiting 类型</p>
<ul>
<li><blockquote>
<p>互斥量是阻塞锁，当某线程无法获取互斥量时，该线程会被直接挂起，该线程不再消耗CPU时间，当其他线程释放互斥量后，操作系统会激活那个被挂起的线程，让其投入运行</p>
</blockquote>
</li>
</ul>
</li>
<li><p>SpinLock: 属于 busy-waiting 类型</p>
<ul>
<li><blockquote>
<p>自旋锁与互斥锁有点类似，只是自旋锁不会引起调用者睡眠，如果自旋锁已经被另外的线程获取，当前线程就一直循环在那里看是否该自旋锁已经释放，”自旋”一词就是因此而得名。其作用是为了解决某项资源的互斥使用。自旋锁由于不会引起调用者睡眠，效率高于互斥锁。</p>
</blockquote>
</li>
<li><blockquote>
<p>自旋锁是一种非阻塞锁，也就是说，如果某线程需要获取自旋锁，但该锁已经被其他线程占用时，该线程不会被挂起，而是在不断的消耗CPU的时间，不停的试图获取自旋锁</p>
</blockquote>
</li>
<li><p>注意: 自旋锁不能用于递归场景, 自己等待自己已经获取的锁会导致死锁 (死锁场景+1).</p>
</li>
<li><p>延伸: 多线程中<strong>死锁</strong>的产生几种情况</p>
<ul>
<li>重复申请锁<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func_1</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mtx.lock();</span><br><span class="line">    func_2();</span><br><span class="line">    mtx.unlock();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func_2</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mtx.lock();</span><br><span class="line">    ...</span><br><span class="line">    mtx.unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<hr>
<ul>
<li>忘记释放锁<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mtx.lock();</span><br><span class="line">    <span class="keyword">if</span> (m_bFlag) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    mtx.unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<hr>
<ul>
<li>多线程申请锁:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func_thread1</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mtx1.lock();</span><br><span class="line">    mtx2.lock();</span><br><span class="line">    <span class="comment">//do something...</span></span><br><span class="line">    mtx2.unlock();</span><br><span class="line">    mtx1.unlock();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">func_thread2</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mtx2.lock();</span><br><span class="line">    mtx1.lock();</span><br><span class="line">    <span class="comment">//do something...</span></span><br><span class="line">    mtx1.unlock();</span><br><span class="line">    mtx2.unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<hr>
</li>
</ul>
</li>
<li><p>场景：双核的机器上有两个线程 (线程A和线程B，分别运行于 coreA 和 coreB), </p>
<ul>
<li>1, 如果线程A想使用pthread_mutex_lock去获取一个临界区的锁，此时该锁被线程B持有，那么此时线程A会被阻塞blocking，coreA会在此时进行上下文切换(<a target="_blank" rel="noopener" href="http://en.wikipedia.org/wiki/Context_switch">Context_switch wiki</a>)将线程A放入等待队列中，此时coreA就可以去运行另一个任务比如执行线程C的任务；</li>
<li>2, 如果线程A想使用pthread_spin_lock去获取一个临界区的锁，此时该锁被线程B持有，那么此时线程A会一直在coreA上进行忙等待并不停的进行锁请求，直到得到此锁为止；</li>
</ul>
</li>
<li><p>两种锁适用于不同场景：</p>
<ul>
<li><blockquote>
<p>1, 如果是多核处理器，如果预计线程等待锁的时间很短，短到比线程两次上下文切换时间要少的情况下，使用自旋锁是划算的;</p>
</blockquote>
</li>
<li><blockquote>
<p>2, 如果是多核处理器，如果预计线程等待锁的时间较长，至少比两次线程上下文切换的时间要长，建议使用互斥量;</p>
</blockquote>
</li>
<li><blockquote>
<p>3, 如果是单核处理器，一般建议不要使用自旋锁。因为，在同一时间只有一个线程是处在运行状态，那如果运行线程发现无法获取锁，只能等待解锁，但因为自身不挂起，所以那个获取到- 锁的线程没有办法进入运行状态，只能等到运行线程把操作系统分给它的时间片用完，才能有机会被调度。这种<br>情况下使用自旋锁的代价很高;</p>
</blockquote>
</li>
<li><blockquote>
<p>4, 如果加锁的代码经常被调用，但竞争情况很少发生时，应该优先考虑使用自旋锁，自旋锁的开销比较小，互斥量的开销较大.</p>
</blockquote>
</li>
<li><blockquote>
<p>5, 中断场景虽然推荐自旋锁但是需要谨慎使用 <a target="_blank" rel="noopener" href="https://blog.csdn.net/zhoutaopower/article/details/86598839">Linux 内核同步（二）：自旋锁（Spinlock）</a></p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>补充：线程上下文切换的代价或者开销</p>
<ul>
<li><blockquote>
<p>我们经常听到线程上下文切换会有一些比较大的开销，那么到底有多大？</p>
</blockquote>
</li>
<li><blockquote>
<p>上下文切换可能出现在线程切换场景，也可能出现在中断场景；</p>
</blockquote>
</li>
<li><p>1，线程切换场景：当cpu从一个线程切换至另一个线程的时候，它需要先存储当前线程的本地数据、指针等，然后加载另一个线程的本地数据、指针等，最后才开始执行。</p>
<ul>
<li><blockquote>
<p>上下文切换通常是计算密集型的，操作系统的许多设计都是为了优化上下文切换的使用。从一个进程切换到另一个进程需要一定的时间来执行管理-保存和加载寄存器和内存映射，更新各种表和列表等。上下文切换中实际涉及的内容取决于体系结构，操作系统和操作系统。共享资源的数量（与不相关的非合作进程相比，属于同一进程的线程共享许多资源。例如，在Linux内核中，上下文切换包括切换寄存器，堆栈指针（它是典型的堆栈指针寄存器）， 程序计数器，刷新翻译后备缓冲区（TLB）并加载要运行的下一个进程的页表（除非旧进程与新进程共享内存）此外，类似的上下文切换发生在用户线程（尤其是绿色线程）之间，并且通常非常轻巧，可以保存和还原最少的上下文。在极端情况下，例如在Go中的goroutine之间切换，上下文切换等效于协程产量，这仅比子例程调用贵一点点。</p>
</blockquote>
</li>
<li><p>解读：即如果使用系统级别的线程上下文切换会涉及到很多资源的保存、更新，但是如果在用户空间比如Java的JVM虚拟机中，上下文切换会消耗较少，绿色线程即运行时库或者VM虚拟机中的线程，可以不依赖操作系统的线程模拟多线程环境，golang中的协程甚至消耗极少类比调用一个函数的消耗。</p>
</li>
</ul>
</li>
<li><p>2，中断场景：</p>
</li>
</ul>
</li>
</ul>
<hr>
<h5 id="linux-中的多种锁"><a href="#linux-中的多种锁" class="headerlink" title="linux 中的多种锁"></a>linux 中的多种锁</h5><ul>
<li><p>pthread中提供的锁有：pthread_mutex_t, pthread_spinlock_t, pthread_rwlock_t</p>
<ul>
<li><blockquote>
<p>1, pthread_mutex_t是互斥锁，同一瞬间只能有一个线程能够获取锁，其他线程在等待获取锁的时候会进入休眠状态。因此pthread_mutex_t消耗的CPU资源很小，但是性能不高，因为会引起线程切换。</p>
</blockquote>
</li>
<li><blockquote>
<p>2, pthread_spinlock_t是自旋锁，同一瞬间也只能有一个线程能够获取锁，不同的是，其他线程在等待获取锁的过程中并不进入睡眠状态，而是在CPU上进入“自旋”等待。自旋锁的性能很高，但是只适合对很小的代码段加锁（或短期持有的锁），自旋锁对CPU的占用相对较高。</p>
</blockquote>
</li>
<li><blockquote>
<p>3, pthread_rwlock_t是读写锁，同时可以有多个线程获得读锁，同时只允许有一个线程获得写锁。其他线程在等待锁的时候同样会进入睡眠。读写锁在互斥锁的基础上，允许多个线程“读”，在某些场景下能提高性能。</p>
</blockquote>
</li>
<li><blockquote>
<p>4, 诸如pthread中的pthread_cond_t, pthread_barrier_t, semaphone等，更像是一种同步原语，不属于单纯的锁。</p>
</blockquote>
</li>
<li><blockquote>
<p>5, 补充：条件变量可以与mutex一起使用，mutex用于线程睡眠，条件变量用于满足该变量后唤醒该线程. 可参考<a target="_blank" rel="noopener" href="https://github.com/Renleilei1992/MyCplusplus/blob/master/Main/195_test_cv_notify.cpp">195_test_cv_notify.cpp 测试条件变量唤醒</a>或者<a target="_blank" rel="noopener" href="https://github.com/Renleilei1992/MyCplusplus/blob/master/Main/194_test_condition_variable_official.cpp">194_test_condition_variable_official.cpp 测试官方示例中的条件变量唤醒</a></p>
</blockquote>
<ul>
<li>以下是网上摘抄的一个性能测试对比,测试的功能是对一个（多线程的环境为：4CPU的电脑上使用四个线程进行同样的度操作，然后取四个线程读取的平均时间）</li>
<li>单线程不加锁：0.818845s</li>
<li>多线程使用pthread_mutex_t：120.978713s </li>
<li>多线程使用pthread_rwlock_t：10.592172s   （多个线程加读锁）</li>
<li>多线程使用pthread_spinlock_t：4.766012s</li>
</ul>
</li>
</ul>
</li>
<li><p>测试程序 <a target="_blank" rel="noopener" href="https://github.com/Renleilei1992/MyCplusplus/blob/master/Main/248_lock_benchmark1.cpp">248_lock_benchmark.cpp</a></p>
<ul>
<li>1, 当我们限定线程数为 2 时，发现spinlock方式的时间消耗低于mutex方式；<ul>
<li><img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/2%E7%BA%BF%E7%A8%8B%E8%87%AA%E6%97%8B%E9%94%81%E4%BA%92%E6%96%A5%E9%94%81%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95.png" alt="2线程自旋锁互斥锁性能测试"></li>
<li>因为此测试环境使用的是单核系统，只有1个cpu核心，单个cpu核心只有1个线程，线程数超过 cpu核心数 x cpu单核线程数 后，spinlock 对比 mutex 方式的消耗开始上升，到一定比例或者数量之后 spinlock 循环等待消耗将大于 mutex 上下文切换的消耗；</li>
</ul>
</li>
<li>2, 当我们限定线程数为 4 或者 更多时，benchmark 结果将反过来，spinlock方式的时间消耗将显著高于mutex方式。<ul>
<li><img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/4%E7%BA%BF%E7%A8%8B%E8%87%AA%E6%97%8B%E9%94%81%E4%BA%92%E6%96%A5%E9%94%81%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95.png" alt="4线程自旋锁互斥锁性能测试"></li>
<li><img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/20%E7%BA%BF%E7%A8%8B%E8%87%AA%E6%97%8B%E9%94%81%E4%BA%92%E6%96%A5%E9%94%81%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95.png" alt="20线程自旋锁互斥锁性能测试"></li>
<li>总结：在不同的应用场景下，自旋锁和互斥锁的使用不同。需要另一台配置好一点的Linux机器进行性能测试对比验证。</li>
</ul>
</li>
<li>3, 对比无锁、GCC原子锁、mutex锁的性能测试<ul>
<li><img src="https://gitee.com/Renleilei1992/MyBlogImgBed/raw/master/img_about_Cplusplus/20%E7%BA%BF%E7%A8%8B%E4%BA%92%E6%96%A5%E9%94%81-atomic-%E6%97%A0%E9%94%81%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95.png" alt="20线程互斥锁-atomic-无锁性能测试"></li>
<li>1，phtread_mutex_t 方式针对多线程处理同一个变量，代价比较高，性能低一些；</li>
<li>2，不加锁多线程操作同一个变量，数据会错乱，得到的结果不对，性能高； – 延申无锁编程(Lock-Free)</li>
<li>3，使用GCC原子锁，有一定的开销但是比mutex锁的开销小一些。</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h5 id="无锁编程（LockFree）"><a href="#无锁编程（LockFree）" class="headerlink" title="无锁编程（LockFree）"></a>无锁编程（LockFree）</h5><hr>
<h5 id="多线程中的volatile-建立语言级别的-memory-barrier"><a href="#多线程中的volatile-建立语言级别的-memory-barrier" class="headerlink" title="多线程中的volatile  (建立语言级别的 memory barrier)"></a>多线程中的volatile  (建立语言级别的 memory barrier)</h5><ul>
<li><blockquote>
<p>当两个线程都要用到某一个变量且该变量的值会被改变时，应该用 volatile 声明，该关键字的作用是防止优化编译器把变量从内存装入 CPU 寄存器中。如果变量被装入寄存器，那么两个线程有可能一个使用内存中的变量，一个使用寄存器中的变量，这会造成程序的错误执行。volatile 的意思是让编译器每次操作该变量时一定要从内存中真正取出，而不是使用已经存在寄存器中的值</p>
</blockquote>
</li>
<li>示例:  <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">volatile  BOOL  bStop  &#x3D;  FALSE;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 线程A</span><br><span class="line">while(  !bStop  )  &#123;  ...  &#125;  </span><br><span class="line">bStop  &#x3D;  FALSE;  </span><br><span class="line">return;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 线程B</span><br><span class="line">bStop  &#x3D;  TRUE;  </span><br><span class="line">while(  bStop  );  &#x2F;&#x2F;等待上面的线程终止，如果bStop不使用volatile申明，那么这个循环将是一个死循环，因为bStop已经读取到了寄存器中，寄存器中bStop的值永远不会变成FALSE，加上volatile，程序在执行时，每次均从内存中读出bStop的值，就不会死循环了。</span><br></pre></td></tr></table></figure>

</li>
</ul>
<hr>
<h5 id="缓存一致性-cache-line相关"><a href="#缓存一致性-cache-line相关" class="headerlink" title="缓存一致性 (cache line相关)"></a>缓存一致性 (cache line相关)</h5><hr>
<h5 id="偏向锁、锁升级等锁延申"><a href="#偏向锁、锁升级等锁延申" class="headerlink" title="偏向锁、锁升级等锁延申"></a>偏向锁、锁升级等锁延申</h5></div></article></div><div id="disqus_thread"></div></div><script>var disqus_shortname = 'JohnnyRian';
var disqus_identifier = '2020/12/17/锁的实现-PV原语/';
var disqus_title = '锁的实现(PV原语)';
var disqus_url = 'http://renleilei1992.gitee.io/2020/12/17/锁的实现-PV原语/';
(function() {
    var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
    dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();</script><script id="dsq-count-scr" src="//JohnnyRian.disqus.com/count.js" async></script></main><footer class="footer-container"><div class="paginator"><a href="/2020/12/07/hello-world/" class="next">下一篇</a></div><div class="copyright"><p>© 2020 <a href="http://renleilei1992.gitee.io">JohnnyRian</a>, powered by <a href="https://hexo.io/" target="_blank">Hexo</a> and <a href="https://github.com/AngryPowman/hexo-theme-prontera" target="_blank">hexo-theme-prontera</a>.</p></div></footer><script>(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;e=o.createElement(i);r=o.getElementsByTagName(i)[0];e.src='//www.google-analytics.com/analytics.js';r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));ga('create',"JohnnyRian",'auto');ga('send','pageview');</script></body></html>